interface: use a global socket instead of per-interface ones
[project/mdnsd.git] / interface.c
1 /*
2 * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
3 * Copyright (C) 2014 Felix Fietkau <nbd@openwrt.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License version 2.1
7 * as published by the Free Software Foundation
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15 #define _GNU_SOURCE
16 #include <sys/socket.h>
17 #include <sys/ioctl.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <sys/utsname.h>
21 #include <net/if.h>
22 #include <netinet/in.h>
23 #include <arpa/inet.h>
24 #include <sys/types.h>
25
26 #include <ifaddrs.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <stdio.h>
31 #include <errno.h>
32
33 #include <libubox/usock.h>
34 #include <libubox/uloop.h>
35 #include <libubox/avl-cmp.h>
36 #include <libubox/utils.h>
37 #include "cache.h"
38 #include "interface.h"
39 #include "util.h"
40 #include "dns.h"
41 #include "announce.h"
42 #include "service.h"
43
44 static struct uloop_fd ufd[] = {
45 [SOCK_UC_IPV4] = { .fd = -1 },
46 [SOCK_UC_IPV6] = { .fd = -1 },
47 [SOCK_MC_IPV4] = { .fd = -1 },
48 [SOCK_MC_IPV6] = { .fd = -1 },
49 };
50
51 static int
52 interface_send_packet4(struct interface *iface, struct sockaddr_in *to, struct iovec *iov, int iov_len)
53 {
54 static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in_pktinfo)) / sizeof(size_t)) + 1];
55 static struct sockaddr_in a = {};
56 static struct msghdr m = {
57 .msg_name = (struct sockaddr *) &a,
58 .msg_namelen = sizeof(a),
59 .msg_control = cmsg_data,
60 .msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)),
61 };
62 struct in_pktinfo *pkti;
63 struct cmsghdr *cmsg;
64 int fd;
65
66 a.sin_family = AF_INET;
67 a.sin_port = htons(MCAST_PORT);
68 m.msg_iov = iov;
69 m.msg_iovlen = iov_len;
70
71 memset(cmsg_data, 0, sizeof(cmsg_data));
72 cmsg = CMSG_FIRSTHDR(&m);
73 cmsg->cmsg_len = m.msg_controllen;
74 cmsg->cmsg_level = IPPROTO_IP;
75 cmsg->cmsg_type = IP_PKTINFO;
76
77 pkti = (struct in_pktinfo*) CMSG_DATA(cmsg);
78 pkti->ipi_ifindex = iface->ifindex;
79
80 fd = ufd[iface->type].fd;
81 if (interface_multicast(iface)) {
82 a.sin_addr.s_addr = inet_addr(MCAST_ADDR);
83 if (to)
84 fprintf(stderr, "Ignoring IPv4 address for multicast interface\n");
85 } else {
86 a.sin_addr.s_addr = to->sin_addr.s_addr;
87 }
88
89 return sendmsg(fd, &m, 0);
90 }
91
92 static int
93 interface_send_packet6(struct interface *iface, struct sockaddr_in6 *to, struct iovec *iov, int iov_len)
94 {
95 static size_t cmsg_data[( CMSG_SPACE(sizeof(struct in6_pktinfo)) / sizeof(size_t)) + 1];
96 static struct sockaddr_in6 a = {};
97 static struct msghdr m = {
98 .msg_name = (struct sockaddr *) &a,
99 .msg_namelen = sizeof(a),
100 .msg_control = cmsg_data,
101 .msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)),
102 };
103 struct in6_pktinfo *pkti;
104 struct cmsghdr *cmsg;
105 int fd;
106
107 a.sin6_family = AF_INET6;
108 a.sin6_port = htons(MCAST_PORT);
109 a.sin6_scope_id = iface->ifindex;
110 m.msg_iov = iov;
111 m.msg_iovlen = iov_len;
112
113 memset(cmsg_data, 0, sizeof(cmsg_data));
114 cmsg = CMSG_FIRSTHDR(&m);
115 cmsg->cmsg_len = m.msg_controllen;
116 cmsg->cmsg_level = IPPROTO_IPV6;
117 cmsg->cmsg_type = IPV6_PKTINFO;
118
119 pkti = (struct in6_pktinfo*) CMSG_DATA(cmsg);
120 pkti->ipi6_ifindex = iface->ifindex;
121
122 fd = ufd[iface->type].fd;
123 if (interface_multicast(iface)) {
124 inet_pton(AF_INET6, MCAST_ADDR6, &a.sin6_addr);
125 if (to)
126 fprintf(stderr, "Ignoring IPv6 address for multicast interface\n");
127 } else {
128 a.sin6_addr = to->sin6_addr;
129 }
130
131 return sendmsg(fd, &m, 0);
132 }
133
134 int
135 interface_send_packet(struct interface *iface, struct sockaddr *to, struct iovec *iov, int iov_len)
136 {
137 if (!interface_multicast(iface) && !to) {
138 fprintf(stderr, "No IP address specified for unicast interface\n");
139 errno = EINVAL;
140 return -1;
141 }
142
143 if (debug > 1) {
144 fprintf(stderr, "TX ipv%d: %s\n", interface_ipv6(iface) ? 6 : 4, iface->name);
145 fprintf(stderr, " multicast: %d\n", interface_multicast(iface));
146 }
147
148 if (interface_ipv6(iface))
149 return interface_send_packet6(iface, (struct sockaddr_in6 *)to, iov, iov_len);
150
151 return interface_send_packet4(iface, (struct sockaddr_in *)to, iov, iov_len);
152 }
153
154 static struct interface *interface_lookup(unsigned int ifindex, enum umdns_socket_type type)
155 {
156 struct interface *iface;
157
158 vlist_for_each_element(&interfaces, iface, node)
159 if (iface->ifindex == ifindex && iface->type == type)
160 return iface;
161
162 return NULL;
163 }
164
165 static void interface_free(struct interface *iface)
166 {
167 announce_free(iface);
168 free(iface->addrs.v4);
169 free(iface);
170 }
171
172 static int
173 interface_valid_src(void *ip1, void *mask, void *ip2, int len)
174 {
175 uint8_t *i1 = ip1;
176 uint8_t *i2 = ip2;
177 uint8_t *m = mask;
178 int i;
179
180 if (cfg_no_subnet)
181 return 0;
182
183 for (i = 0; i < len; i++, i1++, i2++, m++) {
184 if ((*i1 & *m) != (*i2 & *m))
185 return -1;
186 }
187
188 return 0;
189 }
190
191 static void
192 read_socket4(struct uloop_fd *u, unsigned int events)
193 {
194 enum umdns_socket_type type = (enum umdns_socket_type)(u - ufd);
195 struct interface *iface;
196 static uint8_t buffer[8 * 1024];
197 struct iovec iov[1];
198 char cmsg[CMSG_SPACE(sizeof(struct in_pktinfo)) + CMSG_SPACE(sizeof(int)) + 1];
199 struct cmsghdr *cmsgptr;
200 struct msghdr msg;
201 socklen_t len;
202 struct sockaddr_in from;
203 int flags = 0;
204 uint8_t ttl = 0;
205 struct in_pktinfo *inp = NULL;
206 bool valid_src = false;
207
208 if (u->eof) {
209 uloop_end();
210 return;
211 }
212
213 iov[0].iov_base = buffer;
214 iov[0].iov_len = sizeof(buffer);
215
216 memset(&msg, 0, sizeof(msg));
217 msg.msg_name = (struct sockaddr *) &from;
218 msg.msg_namelen = sizeof(struct sockaddr_in);
219 msg.msg_iov = iov;
220 msg.msg_iovlen = 1;
221 msg.msg_control = &cmsg;
222 msg.msg_controllen = sizeof(cmsg);
223
224 len = recvmsg(u->fd, &msg, flags);
225 if (len == -1) {
226 perror("read failed");
227 return;
228 }
229 for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
230 void *c = CMSG_DATA(cmsgptr);
231
232 switch (cmsgptr->cmsg_type) {
233 case IP_PKTINFO:
234 inp = ((struct in_pktinfo *) c);
235 break;
236
237 case IP_TTL:
238 ttl = (uint8_t) *((int *) c);
239 break;
240
241 default:
242 fprintf(stderr, "unknown cmsg %x\n", cmsgptr->cmsg_type);
243 return;
244 }
245 }
246
247 if (!inp)
248 return;
249
250 iface = interface_lookup(inp->ipi_ifindex, type);
251 if (!iface)
252 return;
253
254 if (debug > 1) {
255 char buf[256];
256
257 fprintf(stderr, "RX ipv4: %s\n", iface->name);
258 fprintf(stderr, " multicast: %d\n", interface_multicast(iface));
259 inet_ntop(AF_INET, &from.sin_addr, buf, 256);
260 fprintf(stderr, " src %s:%d\n", buf, ntohs(from.sin_port));
261 inet_ntop(AF_INET, &inp->ipi_spec_dst, buf, 256);
262 fprintf(stderr, " dst %s\n", buf);
263 inet_ntop(AF_INET, &inp->ipi_addr, buf, 256);
264 fprintf(stderr, " real %s\n", buf);
265 fprintf(stderr, " ttl %u\n", ttl);
266 }
267
268 for (size_t i = 0; i < iface->addrs.n_addr; i++) {
269 if (!interface_valid_src((void *)&iface->addrs.v4[i].addr,
270 (void *)&iface->addrs.v4[i].mask,
271 (void *) &from.sin_addr, 4)) {
272 valid_src = true;
273 break;
274 }
275 }
276
277 if (!valid_src)
278 return;
279
280 dns_handle_packet(iface, (struct sockaddr *) &from, ntohs(from.sin_port), buffer, len);
281 }
282
283 static void
284 read_socket6(struct uloop_fd *u, unsigned int events)
285 {
286 enum umdns_socket_type type = (enum umdns_socket_type)(u - ufd);
287 struct interface *iface;
288 static uint8_t buffer[8 * 1024];
289 struct iovec iov[1];
290 char cmsg6[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)) + 1];
291 struct cmsghdr *cmsgptr;
292 struct msghdr msg;
293 socklen_t len;
294 struct sockaddr_in6 from;
295 int flags = 0;
296 int ttl = 0;
297 struct in6_pktinfo *inp = NULL;
298 bool valid_src = false;
299
300 if (u->eof) {
301 uloop_end();
302 return;
303 }
304
305 iov[0].iov_base = buffer;
306 iov[0].iov_len = sizeof(buffer);
307
308 memset(&msg, 0, sizeof(msg));
309 msg.msg_name = (struct sockaddr *) &from;
310 msg.msg_namelen = sizeof(struct sockaddr_in6);
311 msg.msg_iov = iov;
312 msg.msg_iovlen = 1;
313 msg.msg_control = &cmsg6;
314 msg.msg_controllen = sizeof(cmsg6);
315
316 len = recvmsg(u->fd, &msg, flags);
317 if (len == -1) {
318 perror("read failed");
319 return;
320 }
321 for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
322 void *c = CMSG_DATA(cmsgptr);
323
324 switch (cmsgptr->cmsg_type) {
325 case IPV6_PKTINFO:
326 inp = ((struct in6_pktinfo *) c);
327 break;
328
329 case IPV6_HOPLIMIT:
330 ttl = (uint8_t) *((int *) c);
331 break;
332
333 default:
334 fprintf(stderr, "unknown cmsg %x\n", cmsgptr->cmsg_type);
335 return;
336 }
337 }
338
339 if (!inp)
340 return;
341
342 iface = interface_lookup(inp->ipi6_ifindex, type);
343 if (!iface)
344 return;
345
346 if (debug > 1) {
347 char buf[256];
348
349 fprintf(stderr, "RX ipv6: %s\n", iface->name);
350 fprintf(stderr, " multicast: %d\n", interface_multicast(iface));
351 inet_ntop(AF_INET6, &from.sin6_addr, buf, 256);
352 fprintf(stderr, " src %s:%d\n", buf, ntohs(from.sin6_port));
353 inet_ntop(AF_INET6, &inp->ipi6_addr, buf, 256);
354 fprintf(stderr, " dst %s\n", buf);
355 fprintf(stderr, " ttl %u\n", ttl);
356 }
357
358 for (size_t i = 0; i < iface->addrs.n_addr; i++) {
359 if (!interface_valid_src((void *)&iface->addrs.v6[i].addr,
360 (void *)&iface->addrs.v6[i].mask,
361 (void *)&from.sin6_addr, 6)) {
362 valid_src = true;
363 break;
364 }
365 }
366
367 if (!valid_src)
368 return;
369
370 dns_handle_packet(iface, (struct sockaddr *) &from, ntohs(from.sin6_port), buffer, len);
371 }
372
373 static int
374 interface_mcast_setup4(struct interface *iface)
375 {
376 struct ip_mreqn mreq;
377 struct sockaddr_in sa = {};
378 int fd = ufd[SOCK_MC_IPV4].fd;
379
380 sa.sin_family = AF_INET;
381 sa.sin_port = htons(MCAST_PORT);
382 inet_pton(AF_INET, MCAST_ADDR, &sa.sin_addr);
383
384 memset(&mreq, 0, sizeof(mreq));
385 mreq.imr_multiaddr = sa.sin_addr;
386 mreq.imr_ifindex = iface->ifindex;
387 mreq.imr_address.s_addr = iface->addrs.v4[0].addr.s_addr;
388
389 /* Some network drivers have issues with dropping membership of
390 * mcast groups when the iface is down, but don't allow rejoining
391 * when it comes back up. This is an ugly workaround
392 * -- this was copied from avahi --
393 */
394 setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
395 setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
396
397 return 0;
398 }
399
400 static int
401 interface_mcast_setup6(struct interface *iface)
402 {
403 struct ipv6_mreq mreq;
404 struct sockaddr_in6 sa = {};
405 int fd = ufd[SOCK_MC_IPV6].fd;
406
407 sa.sin6_family = AF_INET6;
408 sa.sin6_port = htons(MCAST_PORT);
409 inet_pton(AF_INET6, MCAST_ADDR6, &sa.sin6_addr);
410
411 memset(&mreq, 0, sizeof(mreq));
412 mreq.ipv6mr_multiaddr = sa.sin6_addr;
413 mreq.ipv6mr_interface = iface->ifindex;
414
415 setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq, sizeof(mreq));
416 setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
417
418 return 0;
419 }
420
421 static void interface_start(struct interface *iface)
422 {
423 if (iface->type & SOCKTYPE_BIT_UNICAST)
424 return;
425
426 if (iface->type & SOCKTYPE_BIT_IPV6)
427 interface_mcast_setup6(iface);
428 else
429 interface_mcast_setup4(iface);
430
431 dns_send_question(iface, NULL, C_DNS_SD, TYPE_PTR, 0);
432 announce_init(iface);
433 }
434
435 static bool
436 iface_equal(struct interface *if_old, struct interface *if_new)
437 {
438 size_t addr_size;
439
440 if (if_old->ifindex != if_new->ifindex ||
441 if_old->addrs.n_addr != if_new->addrs.n_addr)
442 return false;
443
444 if (if_old->type & SOCKTYPE_BIT_IPV6)
445 addr_size = sizeof(*if_old->addrs.v6);
446 else
447 addr_size = sizeof(*if_old->addrs.v4);
448 addr_size *= if_old->addrs.n_addr;
449 if (memcmp(if_old->addrs.v4, if_new->addrs.v4, addr_size) != 0)
450 return false;
451
452 return true;
453 }
454
455 static void
456 iface_update_cb(struct vlist_tree *tree, struct vlist_node *node_new,
457 struct vlist_node *node_old)
458 {
459 struct interface *if_old = container_of_safe(node_old, struct interface, node);
460 struct interface *if_new = container_of_safe(node_new, struct interface, node);
461
462 if (if_old && if_new) {
463 if (!iface_equal(if_old, if_new))
464 cache_cleanup(if_old);
465 free(if_old->addrs.v4);
466 if_old->addrs = if_new->addrs;
467 free(if_new);
468 return;
469 }
470
471 if (if_old)
472 interface_free(if_old);
473
474 if (if_new)
475 interface_start(if_new);
476 }
477
478 static int interface_init_socket(enum umdns_socket_type type)
479 {
480 struct sockaddr_in6 local6 = {
481 .sin6_family = AF_INET6
482 };
483 struct sockaddr_in local = {
484 .sin_family = AF_INET
485 };
486 uint8_t ttl = 255;
487 int ittl = 255;
488 int yes = 1;
489 int no = 0;
490 int fd;
491 int af = (type & SOCKTYPE_BIT_IPV6) ? AF_INET6 : AF_INET;
492
493 if (ufd[type].fd >= 0)
494 return 0;
495
496 ufd[type].fd = fd = socket(af, SOCK_DGRAM, 0);
497 if (fd < 0)
498 return -1;
499
500 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
501 #ifdef SO_REUSEPORT
502 setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));
503 #endif
504
505 switch (type) {
506 case SOCK_UC_IPV4:
507 case SOCK_UC_IPV6:
508 break;
509 case SOCK_MC_IPV4:
510 setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
511 setsockopt(fd, IPPROTO_IP, IP_TTL, &ittl, sizeof(ittl));
512 setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &no, sizeof(no));
513 local.sin_port = htons(MCAST_PORT);
514 break;
515 case SOCK_MC_IPV6:
516 setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl));
517 setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));
518 setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes));
519 setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &no, sizeof(no));
520 local6.sin6_port = htons(MCAST_PORT);
521 break;
522 }
523
524 if (type & SOCKTYPE_BIT_IPV6) {
525 ufd[type].cb = read_socket6;
526 if (bind(fd, (struct sockaddr *)&local6, sizeof(local6)) < 0)
527 goto error;
528
529 setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &yes, sizeof(yes));
530 setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &yes, sizeof(yes));
531 } else {
532 ufd[type].cb = read_socket4;
533 if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
534 goto error;
535
536 setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes));
537 setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes));
538 }
539
540 uloop_fd_add(&ufd[type], ULOOP_READ);
541
542 return 0;
543
544 error:
545 close(ufd[type].fd);
546 return -1;
547 }
548
549 static void
550 __interface_add(const char *name, enum umdns_socket_type type,
551 struct interface_addr_list *list)
552 {
553 struct interface *iface;
554 unsigned int ifindex;
555 char *id_buf;
556
557 if (interface_init_socket(type))
558 goto error;
559
560 ifindex = if_nametoindex(name);
561 if (!ifindex)
562 goto error;
563
564 iface = calloc_a(sizeof(*iface),
565 &id_buf, strlen(name) + 3);
566
567 sprintf(id_buf, "%d_%s", type, name);
568 iface->name = id_buf + 2;
569 iface->ifindex = ifindex;
570 iface->type = type;
571 iface->addrs = *list;
572
573 vlist_add(&interfaces, &iface->node, id_buf);
574 return;
575
576 error:
577 free(list->v4);
578 }
579
580 int interface_add(const char *name)
581 {
582 struct ifaddrs *ifap, *ifa;
583 struct interface_addr_list addr4 = {}, addr6 = {};
584
585 getifaddrs(&ifap);
586
587 for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
588 if (strcmp(ifa->ifa_name, name))
589 continue;
590 if (ifa->ifa_addr->sa_family == AF_INET) {
591 struct sockaddr_in *sin;
592
593 if (cfg_proto && (cfg_proto != 4))
594 continue;
595
596 addr4.v4 = realloc(addr4.v4, (addr4.n_addr + 1) * sizeof(*addr4.v4));
597 sin = (struct sockaddr_in *) ifa->ifa_addr;
598 addr4.v4[addr4.n_addr].addr = sin->sin_addr;
599 sin = (struct sockaddr_in *) ifa->ifa_netmask;
600 addr4.v4[addr4.n_addr++].mask = sin->sin_addr;
601 }
602
603 if (ifa->ifa_addr->sa_family == AF_INET6) {
604 uint8_t ll_prefix[] = {0xfe, 0x80 };
605 struct sockaddr_in6 *sin6;
606
607 if (cfg_proto && (cfg_proto != 6))
608 continue;
609
610 sin6 = (struct sockaddr_in6 *) ifa->ifa_addr;
611 if (memcmp(&sin6->sin6_addr, &ll_prefix, 2))
612 continue;
613
614 addr6.v6 = realloc(addr6.v6, (addr6.n_addr + 1) * sizeof(*addr6.v6));
615 sin6 = (struct sockaddr_in6 *) ifa->ifa_addr;
616 addr6.v6[addr6.n_addr].addr = sin6->sin6_addr;
617 sin6 = (struct sockaddr_in6 *) ifa->ifa_netmask;
618 addr6.v6[addr6.n_addr++].mask = sin6->sin6_addr;
619 }
620 }
621
622 freeifaddrs(ifap);
623
624 if (addr4.n_addr) {
625 size_t addr_size = addr4.n_addr * sizeof(*addr4.v4);
626 void *addr_dup = malloc(addr_size);
627
628 memcpy(addr_dup, addr4.v4, addr_size);
629 __interface_add(name, SOCK_UC_IPV4, &addr4);
630 addr4.v4 = addr_dup;
631 __interface_add(name, SOCK_MC_IPV4, &addr4);
632 }
633
634 if (addr6.n_addr) {
635 size_t addr_size = addr6.n_addr * sizeof(*addr6.v6);
636 void *addr_dup = malloc(addr_size);
637
638 memcpy(addr_dup, addr6.v6, addr_size);
639 __interface_add(name, SOCK_UC_IPV6, &addr6);
640 addr6.v6 = addr_dup;
641 __interface_add(name, SOCK_MC_IPV6, &addr6);
642 }
643
644 return !addr4.n_addr && !addr6.n_addr;
645 }
646
647 void interface_shutdown(void)
648 {
649 struct interface *iface;
650
651 vlist_for_each_element(&interfaces, iface, node)
652 if (interface_multicast(iface)) {
653 dns_reply_a(iface, NULL, 0);
654 service_announce_services(iface, NULL, 0);
655 }
656
657 for (size_t i = 0; i < ARRAY_SIZE(ufd); i++) {
658 uloop_fd_delete(&ufd[i]);
659 close(ufd[i].fd);
660 ufd[i].fd = -1;
661 }
662 }
663
664 struct interface *interface_get(const char *name, enum umdns_socket_type type)
665 {
666 char id_buf[32];
667 snprintf(id_buf, sizeof(id_buf), "%d_%s", type, name);
668 struct interface *iface = vlist_find(&interfaces, id_buf, iface, node);
669 return iface;
670 }
671
672 VLIST_TREE(interfaces, avl_strcmp, iface_update_cb, false, false);